/**
	OpenGL Display for MacOS X by Glenn Fiedler <gaffer@gaffer.org>
	I place this code is in the public domain, please use it however you wish!
**/

#ifndef OPENGL_DISPLAY_H
#define OPENGL_DISPLAY_H

#include <OpenGl/gl.h>
#include <OpenGl/glu.h>
#include <OpenGL/glext.h>
#include <OpenGL/OpenGL.h>
#include <Carbon/Carbon.h>

#ifndef FULLSCREEN
#include <AGL/agl.h>
#endif

static pascal OSErr quitEventHandler( const AppleEvent *appleEvt, AppleEvent *reply, SInt32 refcon )
{
	exit( 0 );
	return false;
}

#define QZ_ESCAPE		0x35
#define QZ_PAGEUP		0x74
#define QZ_PAGEDOWN		0x79
#define QZ_RETURN		0x24
#define QZ_UP			0x7E
#define QZ_SPACE		0x31
#define QZ_LEFT			0x7B
#define QZ_DOWN			0x7D
#define QZ_RIGHT		0x7C
#define QZ_W			0x0D
#define QZ_A		    0x00
#define QZ_S			0x01
#define QZ_D			0x02
#define QZ_ONE			0x12
#define QZ_TWO			0x13
#define QZ_THREE		0x14
#define QZ_FOUR			0x15
#define QZ_FIVE			0x17
#define QZ_SIX			0x16
#define QZ_SEVEN		0x1A
#define QZ_EIGHT		0x1C
#define QZ_NINE			0x19
#define QZ_ZERO			0x1D

static bool spaceKeyDown = false;
static bool enterKeyDown = false;
static bool escapeKeyDown = false;
static bool pageUpKeyDown = false;
static bool pageDownKeyDown = false;
static bool upKeyDown = false;
static bool downKeyDown = false;
static bool leftKeyDown = false;
static bool rightKeyDown = false;
static bool wKeyDown = false;
static bool aKeyDown = false;
static bool sKeyDown = false;
static bool dKeyDown = false;
static bool oneKeyDown = false;
static bool twoKeyDown = false;
static bool threeKeyDown = false;
static bool fourKeyDown = false;
static bool fiveKeyDown = false;
static bool sixKeyDown = false;
static bool sevenKeyDown = false;
static bool eightKeyDown = false;
static bool nineKeyDown = false;
static bool zeroKeyDown = false;

pascal OSStatus keyboardEventHandler( EventHandlerCallRef nextHandler, EventRef event, void * userData )
{
	UInt32 eventClass = GetEventClass( event );
	UInt32 eventKind = GetEventKind( event );
	
	if ( eventClass == kEventClassKeyboard )
	{
		char macCharCodes;
		UInt32 macKeyCode;
		UInt32 macKeyModifiers;

		GetEventParameter( event, kEventParamKeyMacCharCodes, typeChar, NULL, sizeof(macCharCodes), NULL, &macCharCodes );
		GetEventParameter( event, kEventParamKeyCode, typeUInt32, NULL, sizeof(macKeyCode), NULL, &macKeyCode );
		GetEventParameter( event, kEventParamKeyModifiers, typeUInt32, NULL, sizeof(macKeyModifiers), NULL, &macKeyModifiers );

		if ( eventKind == kEventRawKeyDown )
		{
			switch ( macKeyCode )
			{
				case QZ_SPACE: spaceKeyDown = true; break;
				case QZ_RETURN: enterKeyDown = true; break;
				case QZ_ESCAPE: escapeKeyDown = true; break;
				case QZ_PAGEUP: pageUpKeyDown = true; break;
				case QZ_PAGEDOWN: pageDownKeyDown = true; break;
				case QZ_UP: upKeyDown = true; break;
				case QZ_DOWN: downKeyDown = true; break;
				case QZ_LEFT: leftKeyDown = true; break;
				case QZ_RIGHT: rightKeyDown = true; break;
				case QZ_W: wKeyDown = true; break;
				case QZ_A: aKeyDown = true; break;
				case QZ_S: sKeyDown = true; break;
				case QZ_D: dKeyDown = true; break;
				case QZ_ONE: oneKeyDown = true; break;
				case QZ_TWO: twoKeyDown = true; break;
				case QZ_THREE: threeKeyDown = true; break;
				case QZ_FOUR: fourKeyDown = true; break;
				case QZ_FIVE: fiveKeyDown = true; break;
				case QZ_SIX: sixKeyDown = true; break;
				case QZ_SEVEN: sevenKeyDown = true; break;
				case QZ_EIGHT: eightKeyDown = true; break;
				case QZ_NINE: nineKeyDown = true; break;
				case QZ_ZERO: zeroKeyDown = true; break;
				
				default:
				{
					// note: for "discovering" keycodes for new keys :)
					/*
					char title[] = "Message";
					char text[64];
					sprintf( text, "key=%x", (int) macKeyCode );
					Str255 msg_title;
					Str255 msg_text;
					c2pstrcpy( msg_title, title );
					c2pstrcpy( msg_text, text );
					StandardAlert( kAlertStopAlert, (ConstStr255Param) msg_title, (ConstStr255Param) msg_text, NULL, NULL);
					*/
					return eventNotHandledErr;
				}
			}
		}
		else if ( eventKind == kEventRawKeyUp )
		{
			switch ( macKeyCode )
			{
				case QZ_SPACE: spaceKeyDown = false; break;
				case QZ_RETURN: enterKeyDown = false; break;
				case QZ_ESCAPE: escapeKeyDown = false; break;
				case QZ_PAGEUP: pageUpKeyDown = false; break;
				case QZ_PAGEDOWN: pageDownKeyDown = false; break;
				case QZ_UP: upKeyDown = false; break;
				case QZ_DOWN: downKeyDown = false; break;
				case QZ_LEFT: leftKeyDown = false; break;
				case QZ_RIGHT: rightKeyDown = false; break;
				case QZ_W: wKeyDown = false; break;
				case QZ_A: aKeyDown = false; break;
				case QZ_S: sKeyDown = false; break;
				case QZ_D: dKeyDown = false; break;
				case QZ_ONE: oneKeyDown = false; break;
				case QZ_TWO: twoKeyDown = false; break;
				case QZ_THREE: threeKeyDown = false; break;
				case QZ_FOUR: fourKeyDown = false; break;
				case QZ_FIVE: fiveKeyDown = false; break;
				case QZ_SIX: sixKeyDown = false; break;
				case QZ_SEVEN: sevenKeyDown = false; break;
				case QZ_EIGHT: eightKeyDown = false; break;
				case QZ_NINE: nineKeyDown = false; break;
				case QZ_ZERO: zeroKeyDown = false; break;

				default: return eventNotHandledErr;
			}
		}
	}

	return noErr;
}

#ifndef FULLSCREEN

	static AGLContext setupAGL( WindowRef window, int width, int height )
	{	
		if ( (Ptr) kUnresolvedCFragSymbolAddress == (Ptr) aglChoosePixelFormat )
			return 0;
	
		GLint attributes[] = { AGL_RGBA, 
			                   AGL_DOUBLEBUFFER, 
			                   AGL_DEPTH_SIZE, 32, 
							   AGL_NO_RECOVERY,
			                   AGL_NONE,
		                   	   AGL_NONE };	

		AGLPixelFormat format = NULL;

		format = aglChoosePixelFormat( NULL, 0, attributes );

		if ( !format ) 
			return 0;

		AGLContext context = aglCreateContext( format, 0 );

		if ( !context )
			return 0;

		aglDestroyPixelFormat( format );

		aglSetWindowRef( context, window );
	
		aglSetCurrentContext( context );

		return context;
	}

	static void cleanupAGL( AGLContext context )
	{
		aglDestroyContext( context );
	}

	WindowRef window;
	AGLContext context;

#else

	CGLContextObj contextObj;

#endif

int GetDisplayWidth()
{
	return CGDisplayPixelsWide( kCGDirectMainDisplay );
}

int GetDisplayHeight()
{
	return CGDisplayPixelsHigh( kCGDirectMainDisplay );
}

void HideMouseCursor()
{
	CGDisplayHideCursor( kCGNullDirectDisplay );
	CGAssociateMouseAndMouseCursorPosition( false );
	CGDisplayMoveCursorToPoint( kCGDirectMainDisplay, CGPointZero );
}

void ShowMouseCursor()
{
	CGAssociateMouseAndMouseCursorPosition( true );
	CGDisplayShowCursor( kCGNullDirectDisplay );
}

bool OpenDisplay( const char title[], int width, int height )
{
#ifndef FULLSCREEN	
	
	int screenWidth = GetDisplayWidth();
	int screenHeight = GetDisplayHeight();

	Rect rect;
	rect.left = ( screenWidth - width ) / 2;
	rect.top = ( screenHeight - height ) / 2;
	rect.right = rect.left + width;
	rect.bottom = rect.top + height;
	
	OSErr result = CreateNewWindow( kDocumentWindowClass, 
					                ( kWindowStandardDocumentAttributes |
//									  kWindowNoTitleBarAttribute |
									  kWindowStandardHandlerAttribute ) &
									 ~kWindowResizableAttribute,
					                &rect, &window );
	
	if ( result != noErr )
		return false;

	SetWindowTitleWithCFString( window, CFStringCreateWithCString( 0, title, CFStringGetSystemEncoding() ) );

	context = setupAGL( window, width, height );

	if ( !context )
		return false;
	
	ShowWindow( window );
	
	SelectWindow( window );

	// install standard event handlers
	
    InstallStandardEventHandler( GetWindowEventTarget( window ) );

#endif

	// install keyboard handler
	
	EventTypeSpec eventTypes[2];

	eventTypes[0].eventClass = kEventClassKeyboard;
	eventTypes[0].eventKind  = kEventRawKeyDown;

	eventTypes[1].eventClass = kEventClassKeyboard;
	eventTypes[1].eventKind  = kEventRawKeyUp;

	EventHandlerUPP handlerUPP = NewEventHandlerUPP( keyboardEventHandler );

	InstallApplicationEventHandler( handlerUPP, 2, eventTypes, NULL, NULL );

#ifdef FULLSCREEN

	// initialize fullscreen CGL
	
	CGDisplayCapture( kCGDirectMainDisplay );
	CGLPixelFormatAttribute attribs[] = 
	{ 
		kCGLPFANoRecovery,
		kCGLPFADoubleBuffer,
	    kCGLPFAFullScreen,
		kCGLPFAStencilSize, ( CGLPixelFormatAttribute ) 8,
	    kCGLPFADisplayMask, ( CGLPixelFormatAttribute ) CGDisplayIDToOpenGLDisplayMask( kCGDirectMainDisplay ),
	    ( CGLPixelFormatAttribute ) NULL
	};
	
	CGLPixelFormatObj pixelFormatObj;
	GLint numPixelFormats;
	CGLChoosePixelFormat( attribs, &pixelFormatObj, &numPixelFormats );

	CGLCreateContext( pixelFormatObj, NULL, &contextObj );

	CGLDestroyPixelFormat( pixelFormatObj );

	CGLSetCurrentContext( contextObj );
	CGLSetFullScreen( contextObj );
	
#endif

	// install quit handler (via apple events)

	AEInstallEventHandler( kCoreEventClass, kAEQuitApplication, NewAEEventHandlerUPP(quitEventHandler), 0, false );

	return true;
}

void UpdateDisplay( int interval = 1 )
{
#ifdef FULLSCREEN
	
	CGLSetParameter( contextObj, kCGLCPSwapInterval, &interval );
	CGLFlushDrawable( contextObj );

#else
	
	GLint swapInterval = interval;
	aglSetInteger( context, AGL_SWAP_INTERVAL, &swapInterval );
	aglSwapBuffers( context );

#endif

	// process events
	
	EventRef event = 0; 
	OSStatus status = ReceiveNextEvent( 0, NULL, 0.0f, kEventRemoveFromQueue, &event ); 
	if ( status == noErr && event )
	{ 
		bool sendEvent = true;

		#ifndef FULLSCREEN
		// note: required for menu bar to work properly
		if ( GetEventClass( event ) == kEventClassMouse && GetEventKind( event ) == kEventMouseDown )
		{
			WindowRef window;
			Point location;
			GetEventParameter( event, kEventParamMouseLocation, typeQDPoint, NULL, sizeof(Point), NULL, &location );
			if ( MacFindWindow( location, &window ) == inMenuBar )
			{
				sendEvent = false;
				MenuSelect( location );
			}
		}
		#endif

		if ( sendEvent )
			SendEventToEventTarget( event, GetEventDispatcherTarget() ); 
		
		ReleaseEvent( event );
	}
}

void CloseDisplay()
{	
#ifdef FULLSCREEN
	
	CGLSetCurrentContext( NULL );
	CGLClearDrawable( contextObj );
	CGLDestroyContext( contextObj );
	CGReleaseAllDisplays();

#else

    cleanupAGL( context );
	context = 0;
	
	DisposeWindow( (WindowPtr) window );
	window = 0;
	
#endif
}

// basic keyboard input

struct Input
{
	static Input Sample()
	{
		Input input;
		
		input.left = leftKeyDown;
		input.right = rightKeyDown;
		input.up = upKeyDown;
		input.down = downKeyDown;
		input.space = spaceKeyDown;
		input.escape = escapeKeyDown;
		input.enter = enterKeyDown;
		input.pageUp = pageUpKeyDown;
		input.pageDown = pageDownKeyDown;
		input.w = wKeyDown;
		input.a = aKeyDown;
		input.s = sKeyDown;
		input.d = dKeyDown;
		input.one = oneKeyDown;
		input.two = twoKeyDown;
		input.three = threeKeyDown;
		input.four = fourKeyDown;
		input.five = fiveKeyDown;
		input.six = sixKeyDown;
		input.seven = sevenKeyDown;
		input.eight = eightKeyDown;
		input.nine = nineKeyDown;
		input.zero = zeroKeyDown;

		return input;
	}
	
	Input()
	{
		left = false;
		right = false;
		up = false;
		down = false;
		space = false;
		escape = false;
		enter = false;
		pageUp = false;
		pageDown = false;
		w = false;
		a = false;
		s = false;
		d = false;
		one = false;
		two = false;
		three = false;
		four = false;
		five = false;
		six = false;
		seven = false;
		eight = false;
		nine = false;
		zero = false;
	}

	bool left;
	bool right;
	bool up;
	bool down;
	bool space;
	bool escape;
	bool enter;
	bool pageUp;
	bool pageDown;
	bool w;
	bool a;
	bool s;
	bool d;
	bool one;
	bool two;
	bool three;
	bool four;
	bool five;
	bool six;
	bool seven;
	bool eight;
	bool nine;
	bool zero;
};

#endif
